package com.evildog.workspace.dependencies.web.aspect;

import cn.hutool.json.JSONUtil;
import com.evildog.workspace.common.result.Result;
import com.evildog.workspace.common.util.IpUtil;
import com.evildog.workspace.common.util.RequestHolder;
import com.evildog.workspace.dependencies.web.component.LogSaver;
import com.evildog.workspace.dependencies.web.util.WsWebUtil;
import com.evildog.workspace.log.api.model.AccessLogParam;
import com.evildog.workspace.security.jwt.core.TokenUserInfo;
import com.evildog.workspace.security.jwt.util.SecurityUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.InputStream;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Arrays;

/**
 * 日志切面
 * <p>
 * OneMall 的日志使用interceptor
 * 但我想额外获取request中发送的内容，在获取的过程中，就涉及到重复从request中重复获取请求内容body，于是发生了一些莫名其妙的问题
 * <p>
 * 所以修改为aop获取access log
 */
@Slf4j
@Component
@Aspect
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "workspace.logger", value = "enable", matchIfMissing = true)
public class ControllerLogAspect {

    @Autowired
    private LogSaver logSaver;


    /**
     * 开始时间
     */
    private static final ThreadLocal<LocalDateTime> START_TIME = new ThreadLocal<>();
    /**
     * 日志信息
     */
    private static final ThreadLocal<AccessLogParam> ACCESS_LOG = new ThreadLocal<>();

    @Value("${spring.application.name}")
    private String applicationName;

//    @Pointcut(value = "@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController)")
    @Pointcut(value="execution(public  * com.evildog..*Controller.*(..))")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object writeLog(ProceedingJoinPoint point) throws Throwable {
        try{
            initAccessLog(point);
        }catch (Exception e) {
            log.error("初始化访问日志失败", e);
        }

        Object result = point.proceed();

        try{
            afterProceed(result);
        }catch (Exception e) {
            log.error("记录请求后日志失败", e);
        }

        return result;
    }

    private void afterProceed(Object result) {
        AccessLogParam accessLogParam = ACCESS_LOG.get();
        accessLogParam.setResponseTime(
                Duration.between(accessLogParam.getStartTime(), LocalDateTime.now()).toMillis());
        if (result != null && result.getClass().equals(Result.class)) {
            Result<?> r = (Result<?>) result;
            accessLogParam.setResultCode(r.getCode())
                    .setResultMessage(r.getMessage())
            ;
        }

        logSaver.addAccessLog(accessLogParam);
    }


    private void initAccessLog(ProceedingJoinPoint point) {
        // 记录当前时间
        LocalDateTime now = LocalDateTime.now();
        START_TIME.set(now);

        HttpServletRequest request = RequestHolder.getRequest();
        TokenUserInfo user = SecurityUtil.getUser();

        AccessLogParam accessLogParam = new AccessLogParam();
        accessLogParam.setApplicationName(applicationName);
        accessLogParam.setStartTime(now);

        if (user != null) {
            accessLogParam.setUserId(user.getUserId());
        }

        if (request != null) {
            accessLogParam.setUri(request.getRequestURI())
                    .setMethod(request.getMethod())
                    .setUserAgent(request.getHeader("User-Agent"))
                    .setIp(IpUtil.getRealIp(request))
            ;

            // 保存请求参数
            // 如果发生异常，将请求参数保存至异常日志表
            Object[] args = point.getArgs();

            // 过滤掉文件流，只记录普通的类
            Object[] newArgs = Arrays.stream(args).filter(arg -> !(arg instanceof File) && !(arg instanceof InputStream) && !(arg instanceof MultipartFile)).toArray();

            WsWebUtil.setRequestBody(request, JSONUtil.toJsonStr(newArgs));
        }

        ACCESS_LOG.set(accessLogParam);
    }





}
